﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Threading;
using Creventive.Wpf.ShellFactory.Workspace.Primitives;

namespace CustomWorkspace.Infrastructure.Workspaces
{
    // This attribute tells WPF that we want the ViewSources property to be filled with the ApplicationViewSources we add directly to the <DetachableDeckWorkspace> tag.
    [ContentProperty("ViewSources")]
    public class DetachableDeckWorkspace : MultiViewWorkspaceBase
    {
        public static readonly RoutedUICommand DetachCommand = new RoutedUICommand("Detach", "Detach", typeof(DetachableDeckWorkspace));

        private static readonly DependencyPropertyKey IsDetachedPropertyKey = DependencyProperty.RegisterAttachedReadOnly("IsDetached", typeof(bool), typeof(DetachableDeckWorkspace), new UIPropertyMetadata(null));
        public static readonly DependencyProperty IsDetachedProperty = IsDetachedPropertyKey.DependencyProperty;

        /// <summary>
        /// Gets a value indicating whether this workspace item has been detached
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <returns></returns>
        public static bool GetIsDetached(WorkspaceItem obj)
        {
            return (bool)obj.GetValue(IsDetachedProperty);
        }

        /// <summary>
        /// Sets a value indicating whether this workspace item has been detached
        /// </summary>
        /// <param name="obj">The obj.</param>
        /// <param name="value">if set to <c>true</c> this view is detached.</param>
        private static void SetIsDetached(WorkspaceItem obj, bool value)
        {
            obj.SetValue(IsDetachedPropertyKey, value);
        }

        static DetachableDeckWorkspace()
        {
            // Override the default style of this control to enable specific styling.
            DefaultStyleKeyProperty.OverrideMetadata(typeof(DetachableDeckWorkspace), new FrameworkPropertyMetadata(typeof(DetachableDeckWorkspace)));
        }

        // This dictionary contains a list of all detached views
        private Dictionary<WorkspaceItem, Window> detachedItems = new Dictionary<WorkspaceItem, Window>();

        public DetachableDeckWorkspace()
        {
            // Connect the detach command to the workspace
            this.CommandBindings.Add(new CommandBinding(DetachCommand, ExecuteDetach));
        }

        private void ExecuteDetach(object sender, ExecutedRoutedEventArgs e)
        {
            // Get the view that should be detached
            var view = e.Parameter as ApplicationView;
                        
            // Get the workspace item for the view
            var item = this.GetItemForView(view);

            // If a view has already been attached, ignore it.
            if (GetIsDetached(item))
                return;
            
            // Create a new container window
            var window = new Window();

            // Register this view as a detached item
            this.detachedItems.Add(item, window);
            SetIsDetached(item, true);

            // Reset the active workspace item if necessary
            if (this.ActiveWorkspaceItem == item)
                ResetActiveItem();
            
            // Create a workspace item presenter which will present the view
            var presenter = new WorkspaceItemPresenter();
            // Add the item to the presenter
            presenter.WorkspaceItem = item;
            // Add the presenter to the window
            window.Content = presenter;

            // Add up some events to manage the view when stuff happens to the window
            window.Closed += this.window_Closed;
            window.Activated += this.window_Activated;
            window.Deactivated += this.window_Deactivated;
            
            // Store the view on the Tag property of the window for convenience
            window.Tag = view;
            
            // Bind up the window's Icon and Title properties to reflect the properties on the view
            window.SetBinding(Window.IconProperty, new Binding() { Source = item, Path = new PropertyPath("Icon") });
            window.SetBinding(Window.TitleProperty, new Binding() { Source = item, Path = new PropertyPath("Title") });

            // We need the window to always use the same data context as the workspace in order to make a view stay in the same context even when detached.
            window.SetBinding(FrameworkElement.DataContextProperty, new Binding() { Source = this, Path = new PropertyPath("DataContext") });

            // Show the window
            window.Show();
            
            // If we don't dispatch the Activate method of the window, it is likely that it will be placed beneath our main window.
            // It's no point activating the window until it has been loaded.
            window.Dispatcher.BeginInvoke(new Func<bool>(window.Activate), DispatcherPriority.Loaded);
        }

        void window_Deactivated(object sender, EventArgs e)
        {
            // Whenever a window is deactivated, tell the view
            var window = (Window)sender;
            var view = (ApplicationView)window.Tag;
            WorkspaceHelper.NotifyDeactivated(view);
        }

        void window_Activated(object sender, EventArgs e)
        {
            // Whenever a window is activated, tell the view
            var window = (Window)sender;
            var view = (ApplicationView)window.Tag;
            WorkspaceHelper.NotifyActivated(view);
        }

        void window_Closed(object sender, EventArgs e)
        {
            // When a window closes, we want to reattach the view.
            var window = (Window)sender;

            // Get the workspace item from the window
            var presenter = (WorkspaceItemPresenter)window.Content;
            var item = presenter.WorkspaceItem;
            // Remove the item from the presenter to allow it to be placed in another container
            presenter.WorkspaceItem = null;
            
            // Unregister the item as a detached window
            this.detachedItems.Remove(item);
            SetIsDetached(item, false);
            
            // Set the active item to the detached view
            this.ActiveWorkspaceItem = item;
        }

        // Selects the last available attached window as the active item
        private void ResetActiveItem()
        {
            // Find the last item (if any) which has not been moved into a window.
            var replacementItem = this.Items.Where(i => !this.detachedItems.ContainsKey(i)).LastOrDefault();
            if (replacementItem != null)
            {
                // If an item was found, activate it.
                this.ActiveWorkspaceItem = replacementItem;
            }
            else
            {
                // Otherwise clear the active item.
                this.ClearActiveWorkspaceItem();
            }
        }

        // This method is called whenever a new view has been added to the workspace, and should be attached.
        // Here we need to create a container item. Then we show the view by setting the ActiveWorkspaceItem property. 
        protected override void OnAddView(ApplicationView view, int targetIndex)
        {
            // Create the view's workspace item
            var workspaceItem = this.CreateItem(view);

            // Set the view as the current view
            this.ActiveWorkspaceItem = workspaceItem;
        }

        // This method is called when an already attached view wants to be activated. In our case this means that 
        // this view should replace the one currently being displayed. This is as simple as in OnAddView. The only 
        // difference is that we retrieve an existing container instead of creating a new one. 
        protected override void OnActivateView(ApplicationView view)
        {
            // Find the view's workspace item
            var workspaceItem = this.GetItemForView(view);

            // Set the view as the current view
            this.ActiveWorkspaceItem = workspaceItem;
        }

        // This method is called when an already attached view should be moved within the workspace. In our case
        // this method has no meaning, so we simply do nothing. 
        protected override void OnMoveView(ApplicationView view, int sourceIndex, int targetIndex)
        {
            // This method has no meaning in this workspace.
        }

        // This method is called when a view has been closed and should be removed from the workspace. 
        protected override void OnRemoveView(ApplicationView view)
        {
            // Find the view's workspace item
            var workspaceItem = this.GetItemForView(view);

            // CHANGE THIS...
            // // If the removed view is the active workspace item, reset it to whatever is next on the activation stack.
            // if (this.ActiveWorkspaceItem == workspaceItem)
            // {
            //     // If there are other items in the workspace, pick the last one and activate it. Otherwise reset the active workspace property.
            //     if (this.Items.Count > 0)
            //         this.ActiveWorkspaceItem = this.Items.Last();
            //     else
            //         this.ClearActiveWorkspaceItem();
            // }
            // TO THIS
            // If the removed view is the active workspace item, reset it to whatever is next on the activation stack.
            if (this.ActiveWorkspaceItem == workspaceItem)
                this.ResetActiveItem();
            // END OF CHANGED CODE

            // If the removed view is a detached view, its window must be closed.
            Window window;
            if (this.detachedItems.TryGetValue(workspaceItem, out window))
                window.Close();

            // Finally destroy the workspace item.
            this.DestroyItem(workspaceItem);
        }
    }
}